/**
 *    Copyright (C) 2008  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

#ifndef __BITMAP_PAGE_HEADER_TEST_H__
#define __BITMAP_PAGE_HEADER_TEST_H__

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/portability/Stream.h>

#include <cubefs/core/tools/bitmap_page.hpp>

using namespace std;

namespace cubefs {
namespace core {
namespace tests {
namespace bitmap {

class PageHeaderTest : public CPPUNIT_NS::TestFixture
{
    CPPUNIT_TEST_SUITE(PageHeaderTest);

    CPPUNIT_TEST(testHeader);
    CPPUNIT_TEST(testUDS);
    CPPUNIT_TEST(testMoveZeroOffset);
    CPPUNIT_TEST(testMoveNonZeroOffset);
    CPPUNIT_TEST(testTrippleMoveNonZeroOffset);
    CPPUNIT_TEST(testTrippleMoveNonZeroOffsetWithPrefix);
    CPPUNIT_TEST(testMoveWithHugeOffset);
    CPPUNIT_TEST(testMoveWithHugeOffsetAndBitmap);

    CPPUNIT_TEST_SUITE_END();

public:

    PageHeaderTest() {
    }

    void setUp() {}

    void tearDown() {}

    void testHeader() {
        bitmap_page_header<32768, 512> header;
        CPPUNIT_ASSERT_EQUAL(248, (int)sizeof(header));

        header.set_size(0x7FFF);
        CPPUNIT_ASSERT_EQUAL(0x7FFFl, header.get_size());

        header.set_size(0xFFFF);
        CPPUNIT_ASSERT_EQUAL(0x7FFFl, header.get_size());

        header.set_offset(0xFFFF);
        CPPUNIT_ASSERT_EQUAL(0xFFFFl, header.get_offset());

        header.set_offset(0x1FFFF);
        CPPUNIT_ASSERT_EQUAL(0xFFFFl, header.get_offset());

        for (long c = 0; c < header.get_max_blocks(); c++) {
            header.set_rank(c, 0x1FF);
            CPPUNIT_ASSERT_EQUAL(0x1FFl, header.get_rank(c));
            header.set_rank(c, 0x7FF);
            CPPUNIT_ASSERT_EQUAL(0x3FFl, header.get_rank(c));
            header.set_rank(c, 0x1AA);

            header.set_bitsize(c, 0x1FF);
            CPPUNIT_ASSERT_EQUAL(0x1FFl, header.get_bitsize(c));
            header.set_bitsize(c, 0x7FF);
            CPPUNIT_ASSERT_EQUAL(0x3FFl, header.get_bitsize(c));
            header.set_bitsize(c, 0x1AA);
            CPPUNIT_ASSERT_EQUAL(0x1AAl, header.get_rank(c));


            header.set_offset(c, 0x3FF);
            CPPUNIT_ASSERT_EQUAL(0x3FFl, header.get_offset(c));
            header.set_offset(c, 0x7FF);
            CPPUNIT_ASSERT_EQUAL(0x3FFl, header.get_offset(c));

            CPPUNIT_ASSERT_EQUAL(0x1AAl, header.get_bitsize(c));
        }
    }

    index_t createUDS(bitmap_page<simple_page_mgr> &page, index_t offset) {
        return createUDS(page, offset, -1);
    }

    index_t createUDS(bitmap_page<simple_page_mgr> &page, index_t offset, index_t label_idx) {
        for (index_t c = 0; c < offset; c++) {
            set_bit(page.bits(), c, 0);
        }

        page.header().set_offset(offset);

        index_t ptr = offset;
        index_t l_idx = 0;

        for (index_t c = 5; c < 105; c++) {
            for (index_t d = 0; d < c; d++) {
                set_bit(page.bits(), ptr++, 1);
            }

            set_bit(page.bits(), ptr++, 0);

            for (index_t d = 0; d < c; d++, ptr+=4, l_idx++) {
                if (l_idx != label_idx) {
                    set_bits(page.bits(), ptr, 9, 4);
                }
                else {
                    ptr -= 4;
                }
            }

            set_bit(page.bits(), ptr++, 0);
        }

        page.header().set_size(ptr);

        for (index_t c = ptr; c < page.get_max_size(); c++) {
            set_bit(page.bits(), c, 0);
        }

        return ptr;
    }

    index_t createSimplePage(bitmap_page<simple_page_mgr> &page, index_t offset, index_t bitmapCount, index_t labelCount) {
        for (index_t c = 0; c < offset; c++) {
            set_bit(page.bits(), c, 0);
        }

        page.header().set_offset(offset);

        if (bitmapCount > 0) {
            index_t c;
            for (c = offset; c < offset + bitmapCount - 1; c++) {
                set_bit(page.bits(), c, 1);
            }
            set_bit(page.bits(), c, 0);
        }

        if (labelCount > 0) {
            index_t c;
            for (c = offset + bitmapCount; c < offset + bitmapCount + labelCount*4; c+=4) {
                set_bits(page.bits(), c, 9, 4);
            }
            set_bit(page.bits(), c, 0);
        }

        index_t size = offset + bitmapCount + labelCount*4 + (labelCount > 0 ? 1 : 0);

        page.header().set_size(size);

        return size;
    }

    index_t get_block(index_t idx) {
        return (idx / 512);
    }

    void count_bits(index_t *rank, index_t *bitsize, index_t size, index_t offset) {
        for (index_t c = 0; c < size; c++) {
            rank[c] = 0;
            bitsize[c] = 0;
        }

        index_t ptr = offset;
        for (index_t c = 5; c < 105; c++) {
            for (index_t d = 0; d < c; d++, ptr++) {
                if (ptr >= 0) {
                    rank[get_block(ptr)] += 1;
                    bitsize[get_block(ptr)] += 1;
                }
            }

            if (ptr >= 0) {
                bitsize[get_block(ptr)] += 1;
            }

            ptr++;

            for (index_t d = 0; d < c; d++, ptr+=4) {}
            ptr++;
        }
    }

    void checkUDS(index_t offset, bitmap_page<simple_page_mgr> &page) {
        const index_t SIZE = 256;
        index_t rank[SIZE], bitsize[SIZE];

        count_bits(rank, bitsize, SIZE, offset);

        index_t total_rank = 0, total_bitsize = 0;
        for (index_t c = 0; c < page.get_blocks(); c++) {
            index_t r = page.header().get_rank(c);
            index_t b = page.header().get_bitsize(c);

            CPPUNIT_ASSERT_EQUAL(rank[c], r);
            CPPUNIT_ASSERT_EQUAL(bitsize[c], b);

            total_rank += r;
            total_bitsize += b;
        }

        CPPUNIT_ASSERT_EQUAL(total_rank, page.header().get_rank());
        CPPUNIT_ASSERT_EQUAL(total_bitsize, page.header().get_bitsize());
    }

    void testUDS() {

        bitmap_page<simple_page_mgr> page;

        index_t size = createUDS(page, 0);

        csize_t prefix = 0, offset = 0;
        page.reindex(false, false, prefix, offset, 4, 0);

        //page.dump(cout);

        CPPUNIT_ASSERT_EQUAL((csize_t)size, offset);
        checkUDS(0, page);

        size = createUDS(page, 100);
        prefix = 0, offset = 100;
        page.reindex(false, false, prefix, offset, 4, 0);

        CPPUNIT_ASSERT_EQUAL((csize_t)size, offset);
        checkUDS(100, page);

        size = createUDS(page, 600);
        prefix = 0, offset = 600;
        page.reindex(false, false, prefix, offset, 4, 0);

        //page.dump(cout);

        CPPUNIT_ASSERT_EQUAL((csize_t)size, offset);
        checkUDS(600, page);

        size = createUDS(page, 100);

        page.header().set_inserted(100);
        page.header().set_hasnt_label(false);

        prefix = 0, offset = 100;
        page.reindex(false, false, prefix, offset, 4, 0);

        CPPUNIT_ASSERT_EQUAL((csize_t)size, offset);
        checkUDS(100, page);

        size = createUDS(page, 0);

        page.shift(3, -3);

        prefix = 3, offset = 0;
        page.reindex(false, false, prefix, offset, 4, 0);

        CPPUNIT_ASSERT_EQUAL(offset, (csize_t)size - 3);
        checkUDS(-3, page);

        prefix = 3, offset = 0;
        page.reindex(true, false, prefix, offset, 4, 0);

        CPPUNIT_ASSERT_EQUAL(offset, (csize_t)size - 3);

        prefix = 4, offset = 0;
        page.reindex(true, true, prefix, offset, 4, 0);

        CPPUNIT_ASSERT_EQUAL((csize_t)size - 3, offset);

        size = createUDS(page, 0, 3);

        page.header().set_inserted(3);
        page.header().set_hasnt_label(true);

        prefix = 0, offset = 0;
        page.reindex(false, false, prefix, offset, 4, 0);

        CPPUNIT_ASSERT_EQUAL((csize_t)size, offset);
    }

    void testMoveZeroOffset() {
        csize_t prefix = 0, offset = 0;

        bitmap_page<simple_page_mgr> page1, page2;
        index_t size = createUDS(page1, 0);

        prefix = 0; offset = 0;
        page1.reindex(false, false, prefix, offset, 4, 0);

        createUDS(page2, 0);

        prefix = 0; offset = 0;
        page2.reindex(false, false, prefix, offset, 4, 0);

        index_t rank1_1 = page1.header().get_rank();
        index_t bitsize1_1 = page1.header().get_bitsize();

        index_t rank2_1 = page2.header().get_rank();
        index_t bitsize2_1 = page2.header().get_bitsize();

        bool inserted = false, hasnt_label = false;
        page1.move_data(page2, 0, inserted, hasnt_label, size - 1024, 4, 0);

        index_t rank1_2 = page1.header().get_rank();
        index_t bitsize1_2 = page1.header().get_bitsize();

        index_t rank2_2 = page2.header().get_rank();
        index_t bitsize2_2 = page2.header().get_bitsize();

        CPPUNIT_ASSERT_EQUAL(size - 1024, page1.header().get_size());
        CPPUNIT_ASSERT_EQUAL(size + 1024, page2.header().get_size());

        CPPUNIT_ASSERT_EQUAL(rank1_1 - rank1_2, rank2_2 - rank2_1);
        CPPUNIT_ASSERT_EQUAL(bitsize1_1 - bitsize1_2, bitsize2_2 - bitsize2_1);
    }

    void testMoveNonZeroOffset() {
        csize_t prefix = 0, offset = 0;

        bitmap_page<simple_page_mgr> page1, page2;
        index_t size = createUDS(page1, 0);

        prefix = 0; offset = 0;
        page1.reindex(false, false, prefix, offset, 4, 0);

        createUDS(page2, 0);

        prefix = 0; offset = 0;
        page2.reindex(false, false, prefix, offset, 4, 0);

        index_t rank1_1 = page1.header().get_rank();
        index_t bitsize1_1 = page1.header().get_bitsize();

        index_t rank2_1 = page2.header().get_rank();
        index_t bitsize2_1 = page2.header().get_bitsize();

        bool inserted = false, hasnt_label = false;
        page1.move_data(page2, 0, inserted, hasnt_label, size - 1050, 4, 0);

        index_t rank1_2 = page1.header().get_rank();
        index_t bitsize1_2 = page1.header().get_bitsize();

        index_t rank2_2 = page2.header().get_rank();
        index_t bitsize2_2 = page2.header().get_bitsize();

        CPPUNIT_ASSERT_EQUAL(size - 1050, page1.header().get_size());
        CPPUNIT_ASSERT_EQUAL(size + 1050, page2.header().get_size());

        CPPUNIT_ASSERT_EQUAL(rank1_1 - rank1_2, rank2_2 - rank2_1);
        CPPUNIT_ASSERT_EQUAL(bitsize1_1 - bitsize1_2, bitsize2_2 - bitsize2_1);
    }

    void testTrippleMoveNonZeroOffset() {
        csize_t prefix = 0, offset = 0;

        bitmap_page<simple_page_mgr> page1, page2;
        index_t size = createUDS(page1, 0);

        prefix = 0; offset = 0;
        page1.reindex(false, false, prefix, offset, 4, 0);

        createUDS(page2, 0);

        prefix = 0; offset = 0;
        page2.reindex(false, false, prefix, offset, 4, 0);

        index_t rank1_1 = page1.header().get_rank();
        index_t bitsize1_1 = page1.header().get_bitsize();

        index_t rank2_1 = page2.header().get_rank();
        index_t bitsize2_1 = page2.header().get_bitsize();

        bool inserted = false, hasnt_label = false;
        page1.move_data(page2, 0, inserted, hasnt_label, size - 1050, 4, 0);

        inserted = false; hasnt_label = false;
        page1.move_data(page2, 0, inserted, hasnt_label, size - 1150, 4, 0);

        inserted = false; hasnt_label = false;
        page1.move_data(page2, 0, inserted, hasnt_label, size - 1450, 4, 0);

        index_t rank1_2 = page1.header().get_rank();
        index_t bitsize1_2 = page1.header().get_bitsize();

        index_t rank2_2 = page2.header().get_rank();
        index_t bitsize2_2 = page2.header().get_bitsize();

        CPPUNIT_ASSERT_EQUAL(size - 1450, page1.header().get_size());
        CPPUNIT_ASSERT_EQUAL(size + 1450, page2.header().get_size());

        CPPUNIT_ASSERT_EQUAL(rank1_1 - rank1_2, rank2_2 - rank2_1);
        CPPUNIT_ASSERT_EQUAL(bitsize1_1 - bitsize1_2, bitsize2_2 - bitsize2_1);
    }

    void testTrippleMoveNonZeroOffsetWithPrefix() {
        csize_t prefix = 0, offset = 0;

        bitmap_page<simple_page_mgr> page1, page2;
        index_t size1 = createUDS(page1, 0, 0);

        page1.shift(3, -3);
        size1 -= 3;

        prefix = 3; offset = 0;
        page1.reindex(true, true, prefix, offset, 4, 0);

        index_t size2 = createUDS(page2, 0);

        prefix = 0; offset = 0;
        page2.reindex(false, false, prefix, offset, 4, 0);

        index_t rank1_1 = page1.header().get_rank();
        index_t bitsize1_1 = page1.header().get_bitsize();

        index_t rank2_1 = page2.header().get_rank();
        index_t bitsize2_1 = page2.header().get_bitsize();

        bool inserted = true, hasnt_label = true;
        page1.move_data(page2, 3, inserted, hasnt_label, size1 - 1050, 4, 0);

        inserted = true; hasnt_label = true;
        page1.move_data(page2, 3, inserted, hasnt_label, size1 - 1150, 4, 0);

        inserted = true; hasnt_label = true;
        page1.move_data(page2, 3, inserted, hasnt_label, size1 - 1450, 4, 0);

        index_t rank1_2 = page1.header().get_rank();
        index_t bitsize1_2 = page1.header().get_bitsize();

        index_t rank2_2 = page2.header().get_rank();
        index_t bitsize2_2 = page2.header().get_bitsize();

        CPPUNIT_ASSERT_EQUAL(size1 - 1450, page1.header().get_size());
        CPPUNIT_ASSERT_EQUAL(size2 + 1450, page2.header().get_size());

        CPPUNIT_ASSERT_EQUAL(rank1_1 - rank1_2, rank2_2 - rank2_1);
        CPPUNIT_ASSERT_EQUAL(bitsize1_1 - bitsize1_2, bitsize2_2 - bitsize2_1);
    }

    void testMoveWithHugeOffset() {
        csize_t prefix = 0, offset = 0;

        bitmap_page<simple_page_mgr> page1, page2;
        index_t size1 = createSimplePage(page1, 10000, 0, 0);

        prefix = 0; offset = 10000;
        page1.reindex(false, false, prefix, offset, 4, 0);

        index_t size2 = createUDS(page2, 0);

        prefix = 0; offset = 0;
        page2.reindex(false, false, prefix, offset, 4, 0);

        index_t rank2_1 = page2.header().get_rank();
        index_t bitsize2_1 = page2.header().get_bitsize();

        bool inserted = true, hasnt_label = true;
        page1.move_data(page2, 0, inserted, hasnt_label, size1 - 1000, 4, 0);

        index_t rank2_2 = page2.header().get_rank();
        index_t bitsize2_2 = page2.header().get_bitsize();

        CPPUNIT_ASSERT_EQUAL(size1 - 1000, page1.header().get_size());
        CPPUNIT_ASSERT_EQUAL(size2 + 1000, page2.header().get_size());
        CPPUNIT_ASSERT_EQUAL(page1.header().get_offset(), size1 - 1000);

        CPPUNIT_ASSERT_EQUAL((index_t)1000, page2.header().get_offset());

        CPPUNIT_ASSERT_EQUAL(rank2_2, rank2_1);
        CPPUNIT_ASSERT_EQUAL(bitsize2_2, bitsize2_1);
    }

    void testMoveWithHugeOffsetAndBitmap() {
        csize_t prefix = 0, offset = 0;

        bitmap_page<simple_page_mgr> page1, page2;
        createSimplePage(page1, 10000, 101, 100);

        prefix = 0; offset = 10000;
        page1.reindex(false, false, prefix, offset, 4, 0);

        CPPUNIT_ASSERT_EQUAL((index_t)10000 + 101 + 100*4 + 1, page1.header().get_size());
        CPPUNIT_ASSERT_EQUAL((index_t)100, page1.header().get_rank());
        CPPUNIT_ASSERT_EQUAL((index_t)101, page1.header().get_bitsize());

        index_t size2 = createUDS(page2, 0);

        prefix = 0; offset = 0;
        page2.reindex(false, false, prefix, offset, 4, 0);

        index_t rank2_1 = page2.header().get_rank();
        index_t bitsize2_1 = page2.header().get_bitsize();

        bool inserted = true, hasnt_label = true;
        page1.move_data(page2, 0, inserted, hasnt_label, 9900, 4, 0);

        CPPUNIT_ASSERT_EQUAL(size2 + 100 + 101 + 400 + 1, page2.header().get_size());

        index_t rank1_2 = page1.header().get_rank();
        index_t bitsize1_2 = page1.header().get_bitsize();

        index_t rank2_2 = page2.header().get_rank();
        index_t bitsize2_2 = page2.header().get_bitsize();

        CPPUNIT_ASSERT_EQUAL((index_t)9900, page1.header().get_size());
        CPPUNIT_ASSERT_EQUAL((index_t)9900, page1.header().get_offset());

        CPPUNIT_ASSERT_EQUAL((index_t)100, page2.header().get_offset());

        CPPUNIT_ASSERT_EQUAL((index_t)0, rank1_2);
        CPPUNIT_ASSERT_EQUAL((index_t)0, bitsize1_2);

        CPPUNIT_ASSERT_EQUAL(rank2_1 + 100, rank2_2);
        CPPUNIT_ASSERT_EQUAL(bitsize2_1 + 101, bitsize2_2);
    }

};

} //bitmap
} //tests
} //core
} //cubefs

#endif
